|
- /**
- * 服务端 API 代理 —— 将 /api/** 请求转发到 ThinkJS 后端
- *
- * 对齐 ai_uniapp_v2 H5 端的 manifest.json devServer.proxy 行为:
- * /api/user/info → https://api.jiefuku.com/user/info
- *
- * 用途:
- * 1. 避免浏览器跨域
- * 2. 服务端转发自动携带 cookie,保持登录态
- *
- * 环境变量 NUXT_PUBLIC_API_BASE 可覆盖目标地址(生产环境指向 https://api.aionline.cc)
- */
- import { proxyRequest, sendWebResponse } from 'h3'
-
- export default defineEventHandler(async (event) => {
- const config = useRuntimeConfig(event)
- const apiBase = String(config.public.apiBase || '').replace(/\/+$/, '')
- if (!apiBase) {
- throw createError({
- statusCode: 500,
- statusMessage: 'NUXT_PUBLIC_API_BASE is not configured',
- })
- }
-
- // 去掉 /api 前缀,得到真实后端路径
- let path = event.path.replace(/^\/api/, '') || '/'
- if (!path.startsWith('/')) path = '/' + path
-
- const method = event.method
- const url = apiBase + path
-
- const headers = {}
-
- // 转发用户 cookie(保持登录态)
- const cookie = event.headers.get('cookie')
- if (cookie) {
- headers.cookie = cookie
- }
-
- // 透传客户端的 Content-Type(multipart/form-data 上传需要)
- const contentType = event.headers.get('content-type')
- if (contentType) {
- headers['content-type'] = contentType
- }
- const isMultipart = Boolean(contentType?.includes('multipart/form-data'))
-
- // 透传签名的 header(nonce / timestr / token)
- const clientNonce = event.headers.get('nonce')
- const clientTimestr = event.headers.get('timestr')
- const clientToken = event.headers.get('token')
- const clientAccept = event.headers.get('accept')
- if (clientNonce) headers.nonce = clientNonce
- if (clientTimestr) headers.timestr = clientTimestr
- if (clientToken) headers.token = clientToken
- if (clientAccept) headers.accept = clientAccept
-
- const fetchOptions = { method, headers }
-
- // GET: 查询参数
- const query = getQuery(event)
- const queryStr = new URLSearchParams(query).toString()
- const fullUrl = queryStr ? url + '?' + queryStr : url
-
- // Multipart 必须直接转发原始请求流。先 readRawBody() 在部分 Chromium /
- // Nitro 组合下会一直等待流结束,导致上传请求既不到后端也不返回。
- if (isMultipart) {
- return proxyRequest(event, fullUrl, { headers })
- }
-
- // POST: 请求体
- if (method === 'POST') {
- const body = await readBody(event)
- if (body && typeof body === 'object') {
- fetchOptions.body = new URLSearchParams(body).toString()
- }
- }
-
- try {
- // SSE 必须透传原始 Web Response;$fetch 会等待响应结束并尝试解析,
- // 导致浏览器无法逐块收到上游事件。
- // Multipart 同样使用原生 fetch,确保二进制请求体和 boundary 原样到达上游。
- if ((clientAccept || '').includes('text/event-stream')) {
- const upstreamResponse = await fetch(fullUrl, fetchOptions)
- return sendWebResponse(event, upstreamResponse)
- }
- return await $fetch(fullUrl, fetchOptions)
- } catch (error) {
- console.error('[Server Proxy Error]', url, error.message)
- return {
- code: 1000,
- data: null,
- msg: '服务器代理请求失败',
- }
- }
- })
|